package biback

import java.lang.{Long => JLong}
import java.math.{BigDecimal => JBigDecimal}

import cats.data.{IorNec, ValidatedNec}
import cats.implicits._
import com.typesafe.scalalogging.Logger
import enumeratum.EnumEntry
import eu.timepit.refined._
import eu.timepit.refined.api.{Refined, Validate}

package object utils {

  trait StateChangeValidation[S <: EnumEntry] {
    type Validated = Either[InvalidStateChange, Unit]

    def canChange(from: S, to: S): Validated = valid

    protected def invalid(from: S, to: S): Validated =
      Left(InvalidStateChange(from.entryName, to.entryName))

    protected def valid: Validated = Right(())

    implicit class EnumStateOps(from: S) {
      def ?~>(to: S): Validated = canChange(from, to)
    }

  }

  implicit class StringRefinedOps(s: String) {
    def check[P](implicit v: Validate[String, P]): Either[RefineError, Refined[String, P]] =
      refineV[P](s).fold(e => Left(RefineError(e)), Right(_))

    def validate[P](implicit v: Validate[String, P]): ValidatedNec[RefineError, Refined[String, P]] =
      check[P].toValidatedNec
  }

  implicit class OptStringRefinedOps(os: Option[String]) {
    def check[P](implicit v: Validate[String, P]): Either[RefineError, Refined[String, P]] =
      os.map(s => refineV[P](s))
        .getOrElse("Option[_] is None".asLeft[Refined[String, P]])
        .fold(e => Left(RefineError(e)), Right(_))

    def validate[P](implicit v: Validate[String, P]): ValidatedNec[RefineError, Refined[String, P]] =
      check[P].toValidatedNec
  }

  implicit class JBigDecimalRefinedOps(j: JBigDecimal) {
    def check[P](implicit v: Validate[BigDecimal, P]): Either[RefineError, Refined[BigDecimal, P]] =
      refineV[P](BigDecimal(j)).fold(e => Left(RefineError(e)), Right(_))

    def validate[P](implicit v: Validate[BigDecimal, P]): ValidatedNec[RefineError, Refined[BigDecimal, P]] =
      check[P].toValidatedNec
  }

  implicit class BigDecimalRefinedOps(b: BigDecimal) {
    def check[P](implicit v: Validate[BigDecimal, P]): Either[RefineError, Refined[BigDecimal, P]] =
      refineV[P](b).fold(e => Left(RefineError(e)), Right(_))

    def validate[P](implicit v: Validate[BigDecimal, P]): ValidatedNec[RefineError, Refined[BigDecimal, P]] =
      check[P].toValidatedNec
  }

  implicit class IntegerRefinedOps(i: Integer) {
    def check[P](implicit v: Validate[Int, P]): Either[RefineError, Refined[Int, P]] =
      refineV[P](i.toInt).fold(e => Left(RefineError(e)), Right(_))

    def validate[P](implicit v: Validate[Int, P]): ValidatedNec[RefineError, Refined[Int, P]] =
      check[P].toValidatedNec
  }

  implicit class IntRefinedOps(i: Int) {
    def check[P](implicit v: Validate[Int, P]): Either[RefineError, Refined[Int, P]] =
      refineV[P](i).fold(e => Left(RefineError(e)), Right(_))

    def validate[P](implicit v: Validate[Int, P]): ValidatedNec[RefineError, Refined[Int, P]] =
      check[P].toValidatedNec
  }

  implicit class JLongRefinedOps(i: JLong) {
    def check[P](implicit v: Validate[Long, P]): Either[RefineError, Refined[Long, P]] =
      refineV[P](Long.unbox(i)).fold(e => Left(RefineError(e)), Right(_))

    def validate[P](implicit v: Validate[Long, P]): ValidatedNec[RefineError, Refined[Long, P]] =
      check[P].toValidatedNec
  }

  implicit class IorNecOps[B](i: IorNec[DomainError, B]) {
    def logTo(log: Logger) = {
      if (i.isLeft) {
        i.left.foreach { a =>
          val content = "\n" + a.toList.map(_.toString).mkString("\n")
          if (i.isBoth) log.debug(content)
          else log.error(content)
        }
      }
      i.right.foreach(b => log.debug(b.toString))
    }
  }
}
